package security.authentification;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;

import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;

import org.hibernate.Session;
import org.hibernate.Transaction;
import org.hibernate.criterion.Restrictions;

import bdd.sql.server.bean.Camera;
import bdd.sql.server.bean.RequestToken;
import bdd.sql.server.bean.ServerSite;
import bdd.sql.server.bean.ServerStorage;
import bdd.sql.server.bean.Video;
import bdd.sql.server.hibernate.HibernateUtil;

import security.aes.AES256;
import tools.StringUtils;

public class Token {
	
	private final String defaultKrakenCode = "KRAKEN2014";
	private final String yes_token = "YES_TOKEN";
	private final String separator = "-";
	private final String field_numDemande = "NUM_DEMAND";
	private final String field_typeDemande = "TYPE_DEMAND";
	private final String field_timeDemande = "TIME_DEMAND";
	private final String field_ipServer = "IP_SERVER";
	private final String field_ipCam = "IP_CAM";
	private final String field_user = "USER";
	private final String field_listVideos = "LIST_VIDEOS";
	
	private final int defaultLength = 1024;
	private final int yesLength = 9;
	private final long validityDuration = 300000000;
	
	private int length;
	private byte[] information;
	private boolean isChecked;
	private HashMap<String,String> fields;
	
	/**
	 * Creation d'un jeton à taille par défaut 1024
	 */
	public Token(){
		this.length = this.defaultLength;
		this.isChecked = false;
		this.fields = new HashMap<String,String>();
		this.initToken();
	}
	
	/**
	 * Creation d'un jeton complet avec heure
	 */
	public Token(int typeDemande, String id_video, String ipServ, String ipCam){
		this.length = this.defaultLength;
		this.isChecked = false;
		this.fields = new HashMap<String,String>();
		this.initToken(typeDemande, id_video, ipServ, ipCam);
	}
	
	/**
	 * Creation d'un jeton complet avec numDemande
	 */
	public Token(int numDemande, int typeDemande, String ipServ, String ipCam){
		this.length = this.defaultLength;
		this.isChecked = false;
		this.fields = new HashMap<String,String>();
		this.initToken(numDemande, typeDemande, ipServ, ipCam);
	}
	
	/**
	 * Creation d'un jeton à taille par défaut 1024
	 */
	public Token(String yes){
		if(yes.equals(yes_token)){
			this.length = this.yesLength;
			this.isChecked = false;
			this.fields = new HashMap<String,String>();
			this.initToken();
		}
	}
	
	/**
	 * Creation d'un jeton à taille choisie
	 * @param len : longueur du jeton désiré
	 */
	public Token(int len){
		this.length = len;
		this.isChecked = false;
		this.fields = new HashMap<String,String>();
		this.initToken();
	}
	
	/**
	 * Creation d'un jeton à partir d'un tableau de bytes existant
	 * @param token : jeton existant
	 */
	public Token(byte[] token){
		this.setInformation(token);
		this.isChecked = false;
		this.length = token.length;
		this.fields = new HashMap<String,String>();
	}
	
	private void initToken(){		
		byte[] tempBuf = null;
		
		if(this.length == yesLength)
			tempBuf = StringUtils.stringToBytes(yes_token);
			//tempBuf = YES_TOKEN.getBytes();
		else
			System.out.println("ErrorInitYesToken");
		
		for(int i = 0; i < tempBuf.length; i++)
			information[i] = tempBuf[i];
		
		
	}
	
	private void initToken(int typeDemande, String id_video, String ipServ, String ipCam){		
		byte[] tempBuf;
		String constructToken = "";
		//long timeNow = new Date().getTime();
		int finalLenght;
		
		Session session = HibernateUtil.getSession();
		Transaction tx = session.beginTransaction();
		RequestToken req = new RequestToken(new Date().getTime());
		session.save(req);
		tx.commit();
		
		//Request req2 = (Request) session.createCriteria(Request.class).
		//		add(Restrictions.like("request_id", req.getRequest_id())).list().get(0);
		
		
		if(this.length == yesLength)
			tempBuf = StringUtils.stringToBytes(yes_token);
			//tempBuf = YES_TOKEN.getBytes();
		else{
			constructToken += defaultKrakenCode+separator;
			constructToken += String.valueOf(req.getRequestTimestamp())+separator;
			constructToken += String.valueOf(req.getRequest_id())+separator;
			constructToken += String.valueOf(typeDemande)+separator;
			if(typeDemande == 1){
				//inclure les ids des videos : id1:id2:id3
				constructToken += id_video+separator;
			}
			else if(typeDemande == 2){
				//inclure l'user + les ids des videos : user:id1:id2:id3
				constructToken += id_video+separator;
			}
			constructToken += ipServ+separator;
			constructToken += ipCam;
			finalLenght = constructToken.length();
			
			this.length = finalLenght;
			this.setInformation(new byte[length]);
			
			tempBuf = StringUtils.stringToBytes(constructToken);
			//tempBuf = StringUtils.stringToBytes(defaultKrakenCode);
		}
		
		for(int i = 0; i < tempBuf.length; i++)
			information[i] = tempBuf[i];
		
		session.close();
	}
	
	private void initToken(int numDemande, int typeDemande, String ipServ, String ipCam){		
		byte[] tempBuf;
		String constructToken = "";
		int finalLenght;
		
		Session session = HibernateUtil.getSession();
		//Transaction tx = session.beginTransaction();
		RequestToken req = (RequestToken) session.createCriteria(RequestToken.class).
				add(Restrictions.like("request_id", numDemande)).list().get(0);		
		
		if(this.length == yesLength)
			tempBuf = StringUtils.stringToBytes(yes_token);
			//tempBuf = YES_TOKEN.getBytes();
		else{
			constructToken += defaultKrakenCode+separator;
			constructToken += String.valueOf(req.getRequestTimestamp())+separator;
			constructToken += String.valueOf(req.getRequest_id())+separator;
			constructToken += String.valueOf(typeDemande)+separator;
			if(typeDemande == 1){
				//inclure les heures de bornes vidéo, flux téléchargé
				constructToken += "heure_départ:heure_fin"+separator;
			}
			constructToken += ipServ+separator;
			constructToken += ipCam;
			finalLenght = constructToken.length();
			
			this.length = finalLenght;
			this.setInformation(new byte[length]);
			
			tempBuf = StringUtils.stringToBytes(constructToken);
			//tempBuf = StringUtils.stringToBytes(defaultKrakenCode);
		}
		
		for(int i = 0; i < tempBuf.length; i++)
			information[i] = tempBuf[i];
		
		session.close();
	}
	
	public boolean checkToken(){
		if(Arrays.equals(information,yes_token.getBytes())){
			System.out.println(yes_token+" detected.");
			this.isChecked = true;
			return true;
		}
		else{
			int cptField = 0;
			//contrôle du flag du Token
			String tokenToCheck = StringUtils.bytesToStringWithoutSpaces(this.information);
			String[] tokenFields = tokenToCheck.split(this.separator); 
			ArrayList<String> list_vid = new ArrayList<String>();
			String save_videos = "";
			boolean checkTokenDebug = false;
			
			if(!tokenFields[cptField].equals(defaultKrakenCode)){
				System.out.println("NoKrakenToken");
				return false;
			}
			else{
				if(checkTokenDebug)	System.out.println("checkTokenDebug_Flag:"+tokenFields[cptField]+" OK");
				cptField++;
				//contrôle de l'expiration de la date
				long controlTime = new Date().getTime();
				long askTime = new Date(Long.valueOf(tokenFields[cptField])).getTime();
				long result_diff = controlTime-askTime; //TODO SERVER NTP
				if(result_diff > this.validityDuration){
					if(checkTokenDebug){
						System.out.println("Debug_ControlTime:"+controlTime);
						System.out.println("Debug_AskTime:"+askTime);
						System.out.println("Debug_Diff:"+result_diff);
						System.out.println("TokenValidityExpired:"+result_diff);
					}
					return false;
				}
				else{
					if(checkTokenDebug)	System.out.println("checkTokenDebug_Date:"+tokenFields[cptField]+" OK");
					this.fields.put(this.field_timeDemande, tokenFields[cptField]);
					cptField++;
					//contrôle de la validité du numéro de demande
					Session session = HibernateUtil.getSession();
					RequestToken req = (RequestToken) session.createCriteria(RequestToken.class).
							add(Restrictions.like("request_id", new Integer(tokenFields[cptField]))).list().get(0);
					session.close();
					
					if(req.getRequestTimestamp() != Long.valueOf(tokenFields[1])){
						if(checkTokenDebug)	System.out.println("InvalidRequestNumber:"+req.getRequestTimestamp()+" & "+Long.valueOf(tokenFields[1]));
						return false;
					}
					else{
						if(checkTokenDebug)	System.out.println("checkTokenDebug_NumDemande:"+tokenFields[cptField]+" OK");
						this.fields.put(this.field_numDemande, tokenFields[cptField]);
						cptField++;
						//contrôle du type de la demande
						if(tokenFields[cptField].equals("0")){
							//streaming!
							if(checkTokenDebug)	System.out.println("checkTokenDebug_TypeDemande:"+tokenFields[cptField]+" Streaming OK");
							this.fields.put(this.field_typeDemande, tokenFields[cptField]);
						}
						else if(tokenFields[cptField].equals("1")){
							//téléchargement vidéo
							if(checkTokenDebug)	System.out.println("checkTokenDebug_TypeDemande:"+tokenFields[cptField]+" Download OK");
							this.fields.put(this.field_typeDemande, tokenFields[cptField]);
							cptField++;

							//on sauve dans une liste les id des vidéos
							//on vérifie cette liste plus bas
							save_videos = tokenFields[cptField];
							String[] videos = tokenFields[cptField].split(":");
							for(String video : videos){
								list_vid.add(video);
							}
						}
						else if(tokenFields[cptField].equals("2")){
							//téléchargement vidéo
							if(checkTokenDebug)	System.out.println("checkTokenDebug_TypeDemande:"+tokenFields[cptField]+" Key OK");
							this.fields.put(this.field_typeDemande, tokenFields[cptField]);
							cptField++;

							//on sauve dans une liste les id des vidéos
							//on vérifie cette liste plus bas
							save_videos = tokenFields[cptField];
							String[] videos = tokenFields[cptField].split(":");
							this.fields.put(this.field_user, videos[0]);
							
							for(int i = 1; i < videos.length; i++){
								list_vid.add(videos[i]);
							}
							/*for(String video : videos){
								list_vid.add(video);
							}*/
						}
						else{
							if(checkTokenDebug)	System.out.println("InvalidTypeNumber:"+tokenFields[cptField]);
							return false;
						}
						cptField++;
						
						//contrôle de l'ip du serv
						session = HibernateUtil.getSession();
						
						//cas 1 : streaming
						if(this.fields.get(this.field_typeDemande).equals("0")){
							ServerSite serverSite = (ServerSite) session.createCriteria(ServerSite.class).
									add(Restrictions.like("siteserv_ip", tokenFields[cptField])).list().get(0);
							session.close();
							
							if(!serverSite.getSiteserv_ip().equals(tokenFields[cptField])){
								if(checkTokenDebug)	System.out.println("InvalidServerIpAddress:"+tokenFields[cptField]);
								return false;
							}
							else{
								if(checkTokenDebug)	System.out.println("checkTokenDebug_ServerIpAddress:"+tokenFields[cptField]+" OK");
								this.fields.put(this.field_ipServer, tokenFields[cptField]);
								cptField++;
								//contrôle de l'ip de la cam
								session = HibernateUtil.getSession();
								Camera camera = (Camera) session.createCriteria(Camera.class).
										add(Restrictions.like("cam_ip", tokenFields[cptField])).list().get(0);
								session.close();
								if(!camera.getCam_ip().equals(tokenFields[cptField])){
									if(checkTokenDebug)	System.out.println("InvalidCameraIpAddress:"+tokenFields[cptField]);
									return false;
								}
								else{
									if(checkTokenDebug)	System.out.println("checkTokenDebug_CameraIpAddress:"+tokenFields[cptField]+" OK");
									this.fields.put(this.field_ipCam, tokenFields[cptField]);
									
									this.isChecked = true;
									return true;
								}
							}
						}
						else if(this.fields.get(this.field_typeDemande).equals("1")
								|| this.fields.get(this.field_typeDemande).equals("2")){
							//cas 2 : telechargement video ou clés
							ServerStorage serverStorage = (ServerStorage) session.createCriteria(ServerStorage.class).
									add(Restrictions.like("storserv_ip", tokenFields[cptField])).list().get(0);
							session.close();
							
							if(!serverStorage.getStorserv_ip().equals(tokenFields[cptField])){
								if(checkTokenDebug)	System.out.println("InvalidServerIpAddress:"+tokenFields[cptField]);
								return false;
							}
							else{
								if(checkTokenDebug)	System.out.println("checkTokenDebug_ServerIpAddress:"+tokenFields[cptField]+" OK");
								this.fields.put(this.field_ipServer, tokenFields[cptField]);
								cptField++;
								//contrôle de l'ip de la cam
								session = HibernateUtil.getSession();
								Camera camera = (Camera) session.createCriteria(Camera.class).
										add(Restrictions.like("cam_ip", tokenFields[cptField])).list().get(0);
								session.close();
								if(!camera.getCam_ip().equals(tokenFields[cptField])){
									if(checkTokenDebug)	System.out.println("InvalidCameraIpAddress:"+tokenFields[cptField]);
									return false;
								}
								else{
									if(checkTokenDebug)	System.out.println("checkTokenDebug_CameraIpAddress:"+tokenFields[cptField]+" OK");
									this.fields.put(this.field_ipCam, tokenFields[cptField]);
									
									//on vérifie que les vidéos demandées sont bien associées à cette caméra
									if(checkTokenDebug)	System.out.print("checkTokenDebug_ListVideo:");
									session = HibernateUtil.getSession();
									save_videos = "";
									for(int i = 0; i < list_vid.size(); i++){
										System.out.print(list_vid.get(i));
										save_videos += list_vid.get(i);
										if(i != (list_vid.size()-1)){
											System.out.print(":");
											save_videos += ":";
										}
										Video video = (Video) session.createCriteria(Video.class).
												add(Restrictions.like("video_id", Integer.parseInt(list_vid.get(i)))).list().get(0);
										if(!video.getCamera().getCam_ip().equals(camera.getCam_ip())){
											if(checkTokenDebug)	System.out.println("InvalidVideosSelected:"+tokenFields[cptField-2]+"|CamId:"+camera.getCam_id());
											return false;
										}
									}
									System.out.print("\n");
									session.close();
									this.fields.put(this.field_listVideos, save_videos);
									
									this.isChecked = true;
									return true;
								}
							}
						}
						else{
							if(checkTokenDebug)	System.out.println("checkTokenDebug_UnknownDemandType");
							return false;
						}
						
					}
				}
			}
		}
	}
	
	public byte[] getInformation() {	return information;	}
	public void setInformation(byte[] information) {	this.information = information;	}
		
	public int getLength() {	return length;	}
	public void setLength(int length) {	this.length = length;	}
	
	public boolean isTokenChecked(){	return this.isChecked;	}
	
	public String getTokenTime(){	return this.fields.get(this.field_timeDemande);	}
	public String getTokenNumDemande(){	return this.fields.get(this.field_numDemande);	}
	public String getTokenTypeDemande(){	return this.fields.get(this.field_typeDemande);	}
	public String getTokenIPServer(){	return this.fields.get(this.field_ipServer);	}
	public String getTokenIPCam(){	return this.fields.get(this.field_ipCam);	}
	public String getTokenUser(){	return this.fields.get(this.field_user);	}
	public String getTokenListVideos(){	return this.fields.get(this.field_listVideos);	}
	

	/**
	 * @param args
	 */
	public static void main(String[] args) {
		//tester création/parsage/chiffrement/déchiffrement...
		/*Token token = new Token("YES_TOKEN");
		System.out.println(StringUtils.bytesToStringWithoutSpaces(token.getInformation()));
		System.out.println(token.checkToken());
		//System.out.println(token.information.toString());
		
		System.out.println("\nTests chiffrements Token:");
		
		final char[] ksPassword = {'t','o','t','o'};
		final char[] keyPassword = {'t','o','t','o'};
		String alias = "aesKey";
		String ksPath = "C:\\Documents and Settings\\Guillaume\\workspace\\vsm_M2_reseau\\bin\\aesKeyStore.keystore";
		SecretKey aesKey = AES256.loadKeyFromKeyStore(ksPath, ksPassword, keyPassword, alias);

		// Init cipher
		byte[] iv = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
	    IvParameterSpec ivspec = new IvParameterSpec(iv);
		Cipher encryptCipher = AES256.initCipherEncrypt(aesKey, ivspec);

		// Crypt and Decrypt
		System.out.println(StringUtils.bytesToStringWithoutSpaces(token.getInformation()));
		byte[] encryptedData = AES256.encrypt(token.getInformation(),aesKey,encryptCipher);
		
		String temp1 = StringUtils.bytesToString(encryptedData);
		
		SecretKey aesKey2 = AES256.loadKeyFromKeyStore(ksPath, ksPassword, keyPassword, alias);
		Cipher decryptCipher = AES256.initCipherDecrypt(aesKey2, ivspec);
		
		byte[] decryptedData = AES256.decrypt(StringUtils.stringToBytes(temp1),aesKey2,decryptCipher);
		
		Token decryptedToken = new Token(decryptedData);

		// Show Result
		System.out.println("Résultat:\t"+new String(decryptedData));
		System.out.println("Résultat2:\t"+new String(decryptedToken.getInformation()));*/
		
		//tests jetons complets
		Token fullToken_streamn = new Token(0,"0","192.168.2.41","192.168.2.43");
		System.out.println(StringUtils.bytesToStringWithoutSpaces(fullToken_streamn.getInformation()));
		System.out.println(fullToken_streamn.checkToken());
		
		Token fullToken_down = new Token(1,"1:5:8:12","192.168.2.41","192.168.2.43");
		System.out.println(StringUtils.bytesToStringWithoutSpaces(fullToken_down.getInformation()));
		System.out.println(fullToken_down.checkToken());
	}

}
